一般在電腦中儲存顏色是使用3個byte的整數,比如16777215就代表了白色,不過這樣的表示方法很難看得出顏色的資訊,所以寫程式時通常會使用16進位的格式,比如剛剛說的白色以16進位的格式就會寫成0xFFFFFF,其中開頭的0x是一個專門給電腦看的標記,表示接在後面的是個16進位的數字。
在0x後面接的是三組兩個字元的字串,分別表示紅色(R)、綠色(G)、藍色(B)的亮度,最暗是00,最亮是FF,代表的是16進位的最小值和最大值,換算成十進位的話,就會是0到255。於是上面舉的例子,0xFFFFFF就代表三原色RGB都是FF,也就是最亮的狀態,合起來就是白色,以此類推,0xFF0000就是紅色,0x00FF00就是綠色,而0xFFFF00就是黃色。
若想讓顏色也套用靠近演算法去接近目標顏色的時候,我們可以讓顏色中的RGB分別套用靠近演算法去得到一個更靠近目標色的顏色。
使用RGB去靠近目標色的問題在於,顏色的本質其實不是RGB那麼單純,我們在電腦中採用RGB作為三原色並不是一個「本來就是這樣啊」的決定。在色彩學中,理論上只要是三個飽和度100%、色相相差120度的顏色都可以當作三原色。
RGB的顏色空間是一個正方形,使用RGB來執行靠近演算法,其實就是讓顏色在這個RGB的空間裏進行空間位置的靠近。
顏色除了能在RGB空間找到位置,其實還可以出現在其他更多的色彩空間,比如印刷時用的CMYK、電視常用的xvYCC、以及繪圖師常用的HSB等等。
現在我們就來看看繪圖軟體中最常見的HSB色彩空間。
HSB指的是:
關於顏色在HSB和RGB兩個空間中的轉換,可以參考小弟的另一篇文章:
《科普一下顏色在不同空間轉換的意義和演算法》
為什麼不用RGB就好,非要看這什麼HSB呢?那是因為在色彩學中,很多理論和技巧都圍繞在HSB的色彩空間,像是互補色、三分色、補色分割、相似色等等,而這些顏色的性質在RGB空間中比較難加以操作。
所以大家可能猜到我想做什麼了是吧?
我們可以把顏色從RGB轉換到HSB空間,然後在HSB空間中對色相、飽和度、亮度分別進行靠近演算法,計算完成後,再把新的顏色轉回RGB空間就行了。
使用HSB空間的靠近演算法,可以避免顏色在RGB空間靠近時彩度降低再回升的問題,但也會造成色相變化較大的效果,所以在HSB空間靠近的時候,有時會特別降低Hue(色相)的靠近率。
我們先假設程式中已經有了昨天和前天寫好的兩種靠近演算法。
/** 靠近演算法 */
function numberFollowTarget(current: number, target: number, rate: number): number;
/** 角度靠近演算法 */
function degreeFollowTarget(current: number, target: number, rate: number): number;
然後我們要實作HSB空間的靠近演算法。
程式中會用到CG提供的Color類別,如果想看原始碼的同學,可以點擊下面的連結。
Color.ts
// 先定義一個開始的顏色
let current = new Color(0xFFDD00);
// 然後定義目標顏色
let target = new Color(0x00FF55);
/** 在進行更新前要注意我們必須在更新的整個過程中,
* 全部都使用HSB來進行計算
* 才不會在整數/小數之間轉換時產生失真誤差
*/
let currentHSB = current.toHSB();
let targetHSB = target.toHSB();
/** 定義靠近率 */
let followRate = 0.1;
/** 宣告遊戲更新的函式 */
function updateGame(): void {
// 對飽和度(S)和亮度(B)分別用靠近演算法計算新值
currentHSB.brightness = numberFollowTarget(
currentHSB.brightness,
targetHSB.brightness,
followRate);
currentHSB.saturation = numberFollowTarget(
currentHSB.saturation,
targetHSB.saturation,
followRate);
// 對色相(H)必須要使用角度專用的靠近演算法
currentHSB.hue = degreeFollowTarget(
currentHSB.hue,
targetHSB.hue,
followRate);
// 最後把HSB轉換回RGB的空間
current.fromHSB(
currentHSB.hue,
currentHSB.saturation,
currentHSB.brightness);
// 列印一下現在的顏色16進位碼
console.log('顏色碼 = ' + current.colorHex);
}
// 將updateGame安排進每幀執行函式的列表裏
cg.addUpdateFunction(updateGame);
這樣就能在不失去彩度的情況下,進行顏色的靠近演算法了。
當然,很多情況直接使用RGB空間的靠近就很棒了,所以什麼時候該用什麼方法,就是程式設計師(或美術總監)要去考慮的問題了。